package edu.ntnu.superrss.service;

import edu.ntnu.superrss.models.User;
import java.net.MalformedURLException;
import java.util.List;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.fetcher.FetcherException;
import com.sun.syndication.io.FeedException;
import edu.ntnu.superrss.models.Category;
import edu.ntnu.superrss.models.SyndFeedImpl;
import edu.ntnu.superrss.utils.HibernateUtil;
import edu.ntnu.superrss.utils.feedgrabber.UrlWorker;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.stereotype.Service;

/**
 * FeedServiceImpl is an implementation of FeedService. The main
 * objective of this class is to act as a service layer for controler objects.
 * Database calls are placed in this class for persistence, a seperate DAO implementation
 * could also have been used.
 *
 */
@Service
public class FeedServiceImpl implements FeedService {

    /**
     * Adds a SyndFeed object to a user
     * <p>
     * This method adds a SyndFeed object to a user and stores it in the database
     * It uses uniqueResult of the Query interface since it expects a single result, an exception will be thrown
     * if more than one match is found
     *
     * @param  url  a String with the feed URL to be added
     * @param  user a String with the name of the user to add the feed to
     */
    public SyndFeedImpl addFeed(String user, String url) throws HibernateException, NullPointerException, MalformedURLException, IllegalArgumentException, IOException, FeedException, FetcherException {
        Logger.getLogger(FeedServiceImpl.class.getName()).log(Level.INFO, "FeedService::addFeed(User " + user + ", String " + url + ")");
        User properUser;
        SyndFeedImpl feed = (SyndFeedImpl) UrlWorker.grabFeed(new URL(url));
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();

            properUser = (User) session.createQuery("from User where username = ?)").setParameter(0, user).uniqueResult();

            SyndFeedImpl dbfeed = (SyndFeedImpl) session.createQuery("From SyndFeedImpl where url = ?").setParameter(0, url).uniqueResult();
            if (dbfeed != null) {
//            feed.setId(dbfeed.getId());
                feed = dbfeed;
            } else {
                session.save(feed);//DeleteOrphan krever dette
            }
            properUser.addFeed(feed);
            session.merge(properUser);
            session.flush();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e; // TODO: LOG4J
        } finally {
            session.close();
        }
        return feed;

    }

    /**
     * Returns all SyndFeed objects from the user that belongs to the specified User
     *
     * <p>
     *
     * @param  user a String representation of the User, used to identify which SyndFeed objects to get
     * @return  the list with SyndFeed elements or null if an error occured
     * @throws  HibernateException
     */
    public List getFeeds(String username) throws HibernateException, NullPointerException {

        User properUser;
        List<SyndFeed> l;

        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();

            properUser = (User) session.createQuery("from User where username = ?").setParameter(0, username).uniqueResult();
            l = new ArrayList<SyndFeed>(properUser.getFeeds());

            for (SyndFeed feed : l) {
                Hibernate.initialize(feed.getCategories());
            }

            session.flush();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e; // TODO: LOG4J
        } finally {
            session.close();
        }
        return l;

    }

    public List getFeedsByCategory(String username, Long categoryFilterId) {
        User properUser;
        List<SyndFeedImpl> l;
        List<SyndFeedImpl> filteredFeeds = new ArrayList<SyndFeedImpl>();

        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();

            properUser = (User) session.createQuery("from User where username = ?)").setParameter(0, username).uniqueResult();
            l = new ArrayList(properUser.getFeeds());

            boolean feedInCategory = false;
            for (SyndFeedImpl feed : l) {
                Hibernate.initialize(feed.getCategories());
                List<Category> categories = feed.getCategories();

                feedInCategory = false;
                for (Category category : categories) {
                    if (category.getId().equals(categoryFilterId)) {
                        feedInCategory = true;
                    }
                }

                if (feedInCategory) {
                    filteredFeeds.add(feed);
                }
            }

            session.flush();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e; // TODO: LOG4J
        } finally {
            session.close();
        }
        
        return filteredFeeds;
    }

    /**
     * Removes an SyndFeed object from a user and updates the database
     * <p>
     * This method removes a SyndFeed element with from a user object and updates the user in the database
     * It uses uniqueResult of the Query interface since it expects a single result, an exception will be thrown
     * if more than one match is found
     *
     * @param user the User object to be modified, a fully qualified User object is expected
     * @param  id a Long with the primary key of the SyndFeed in database
     * @throws  HibernateException and NullPointerException. Nullpointer is strictly speaking a HibernateException in this
     *          context, but NullPointerException wil be thrown if the user can not be found
     */
    public void removeFeed(User user, Long id) throws HibernateException, NullPointerException {
        Logger.getLogger(FeedServiceImpl.class.getName()).log(Level.INFO, "FeedService::removeFeed(User " + user.getUsername() + ", " + "Long " + id);


        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();

            SyndFeed sf = user.getFeed(id);
            user.removeFeed(sf);//burde egentlig kalle en egen UserService
            session.merge(user);

            session.flush();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e; // TODO: LOG4J
        } finally {
            session.close();
        }

        // us.updateUser(user);

    }

    /**
     * Removes an SyndFeed object from a user and updates the database
     * <p>
     * This method removes a SyndFeed element with from a user object and updates the user in the database
     * It uses uniqueResult of the Query interface since it expects a single result, an exception will be thrown
     * if more than one match is found. This method expects a String representation of the User object, that is the username.
     *
     * @param user the String username of the User object to be modified
     * @param  id  a Long with the primary key of the SyndFeed in database
     * @throws  HibernateException and NullPointerException. Nullpointer is strictly speaking a HibernateException in this
     *          context, but NullPointerException wil be thrown if the user can not be found
     */
    public void removeFeed(String username, Long id) throws HibernateException, NullPointerException {
        Logger.getLogger(FeedServiceImpl.class.getName()).log(Level.INFO, "FeedService::removeFeed(User " + username + ", " + "Long " + id);
        User user;

        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
            user = (User) session.createQuery("from User where username = ?)").setParameter(0, username).uniqueResult();
            SyndFeed sf = user.getFeed(id);
            user.removeFeed(sf);//burde egentlig kalle en egen UserService
            session.saveOrUpdate(user);
            session.flush();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e; // TODO: LOG4J
        } finally {
            session.close();
        }
    }

    /**
     * Returns a SyndFeed object with specified id from the database
     * <p>
     *
     * @param  id  a Long with the primary key of the SyndFeed in database
     * @return SyndFeed the SyndFeed object with matching id
     */
    public SyndFeedImpl getFeed(Long id) {
        Logger.getLogger(FeedServiceImpl.class.getName()).log(Level.INFO, "FeedService::getFeed(Long " + id + ")");

        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = null;
        SyndFeedImpl syndFeed = null;
        try {
            tx = session.beginTransaction();
            syndFeed = (SyndFeedImpl) session.createQuery("From SyndFeedImpl WHERE id = ?").setParameter(0, id).uniqueResult();
            session.flush();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e; // TODO: LOG4J
        } finally {
            session.close();
        }
        return syndFeed;
    }

    public void updateFeed(SyndFeedImpl feed) throws HibernateException {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
            session.saveOrUpdate(feed);
            session.flush();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e; // TODO: LOG4J
        } finally {
            session.close();
        }
    }
}
